/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package org.netbeans.modules.j2ee.core.support.java.method;

import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.swing.DefaultCellEditor;
import javax.swing.JComboBox;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableColumn;
import javax.swing.text.JTextComponent;
import org.netbeans.api.java.source.ClasspathInfo;
import org.netbeans.modules.j2ee.core.api.support.java.method.MethodModel;
import org.openide.util.NbBundle;
import org.openide.util.Utilities;

/**
 *
 * @author  Martin Adamek
 */
public final class ParametersPanel extends javax.swing.JPanel {
    
    private static final int COL_NAME_INDEX = 0;
    private static final int COL_TYPE_INDEX = 1;
    private static final int COL_FINAL_INDEX = 2;
    
    private static final String[] columnNames = {
        NbBundle.getMessage(ParametersPanel.class, "ParametersPanel.LBL_Name"),  // NOI18N
        NbBundle.getMessage(ParametersPanel.class, "ParametersPanel.LBL_Type"),  // NOI18N
        NbBundle.getMessage(ParametersPanel.class, "ParametersPanel.LBL_Final"),  // NOI18N
    };
    
    public static final String SELECTION = "selection"; // NOI18N
    
    private final ParamsTableModel tableModel;

    public ParametersPanel(ClasspathInfo cpInfo, List<MethodModel.Variable> parameters) {
        initComponents();
        
        tableModel = new ParamsTableModel(parameters);
        table.setModel(tableModel);
        tableModel.addTableModelListener(new TableModelListener() {
            public void tableChanged(TableModelEvent e) {
                firePropertyChange("parameters", null, null); // NOI18N
            }
        });

        if (parameters.isEmpty()) {
            table.setFocusable(false);
        }
        
        JComboBox typeCombo = new JComboBox();
        
        ReturnTypeUIHelper.connect(typeCombo, cpInfo);
        TableColumn typeTableColumn = table.getColumnModel().getColumn(COL_TYPE_INDEX);
        typeTableColumn.setCellEditor(new DefaultCellEditor(typeCombo));

        // #147884 fix
        typeCombo.addActionListener(new TypeComboListener(typeCombo));
        
        table.setRowHeight(typeCombo.getPreferredSize().height);
        table.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE); // NOI18N
        
        ListSelectionListener listSelectionListener = new ListSelectionListenerImpl();
        table.getSelectionModel().addListSelectionListener(listSelectionListener);
        table.getColumnModel().getSelectionModel().addListSelectionListener(listSelectionListener);

        table.addMouseListener(new MouseAdapter() {
            @Override
            public void mousePressed(MouseEvent e) {
                updateButtons();
            }
        });
        table.addKeyListener(new KeyAdapter() {
            @Override
            public void keyReleased(KeyEvent e) {
                updateButtons();
            }
        });
        
        updateButtons();
    }
    
    public List<MethodModel.Variable> getParameters() {
        return tableModel.getParameters();
    }

    /** This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        jScrollPane1 = new javax.swing.JScrollPane();
        table = new javax.swing.JTable();
        addButton = new javax.swing.JButton();
        removeButton = new javax.swing.JButton();
        upButton = new javax.swing.JButton();
        downButton = new javax.swing.JButton();

        table.setModel(new javax.swing.table.DefaultTableModel(
            new Object [][] {

            },
            new String [] {

            }
        ));
        jScrollPane1.setViewportView(table);
        table.getAccessibleContext().setAccessibleName(org.openide.util.NbBundle.getMessage(ParametersPanel.class, "ACSN_ParametersTab")); // NOI18N
        table.getAccessibleContext().setAccessibleDescription(org.openide.util.NbBundle.getMessage(ParametersPanel.class, "ACSD_ParametersTab")); // NOI18N

        org.openide.awt.Mnemonics.setLocalizedText(addButton, org.openide.util.NbBundle.getMessage(ParametersPanel.class, "ParametersPanel.addButton.text")); // NOI18N
        addButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                addButtonActionPerformed(evt);
            }
        });

        org.openide.awt.Mnemonics.setLocalizedText(removeButton, org.openide.util.NbBundle.getMessage(ParametersPanel.class, "ParametersPanel.removeButton.text")); // NOI18N
        removeButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                removeButtonActionPerformed(evt);
            }
        });

        org.openide.awt.Mnemonics.setLocalizedText(upButton, org.openide.util.NbBundle.getMessage(ParametersPanel.class, "ParametersPanel.upButton.text")); // NOI18N
        upButton.setEnabled(false);
        upButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                upButtonActionPerformed(evt);
            }
        });

        org.openide.awt.Mnemonics.setLocalizedText(downButton, org.openide.util.NbBundle.getMessage(ParametersPanel.class, "ParametersPanel.downButton.text")); // NOI18N
        downButton.setEnabled(false);
        downButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                downButtonActionPerformed(evt);
            }
        });

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
        this.setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 241, Short.MAX_VALUE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addComponent(addButton)
                    .addComponent(removeButton)
                    .addComponent(upButton)
                    .addComponent(downButton))
                .addContainerGap())
        );

        layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {addButton, downButton, removeButton, upButton});

        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addGroup(layout.createSequentialGroup()
                        .addComponent(addButton)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(removeButton)
                        .addGap(22, 22, 22)
                        .addComponent(upButton)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(downButton))
                    .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 276, Short.MAX_VALUE))
                .addContainerGap())
        );

        addButton.getAccessibleContext().setAccessibleDescription(org.openide.util.NbBundle.getMessage(ParametersPanel.class, "ACSD_AddParameterIntoTab")); // NOI18N
        removeButton.getAccessibleContext().setAccessibleDescription(org.openide.util.NbBundle.getMessage(ParametersPanel.class, "ACSD_RemoveParameterIntoTab")); // NOI18N
        upButton.getAccessibleContext().setAccessibleDescription(org.openide.util.NbBundle.getMessage(ParametersPanel.class, "ACSD_MoveUpInParamTab")); // NOI18N
        downButton.getAccessibleContext().setAccessibleDescription(org.openide.util.NbBundle.getMessage(ParametersPanel.class, "ACSD_MoveDownInParamTab")); // NOI18N
    }// </editor-fold>//GEN-END:initComponents

private void upButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_upButtonActionPerformed
    int selIndex = table.getSelectedRow();
    int newIndex = selIndex - 1;
    if (newIndex >= 0) {
        tableModel.set(newIndex, tableModel.set(selIndex, tableModel.getParameter(newIndex)));
        table.setRowSelectionInterval(newIndex, newIndex);
        updateButtons();
    }
}//GEN-LAST:event_upButtonActionPerformed

private void downButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_downButtonActionPerformed
    int selIndex = table.getSelectedRow();
    int newIndex = selIndex + 1;
    if (newIndex < tableModel.getParameters().size()) {
        tableModel.set(newIndex, tableModel.set(selIndex, tableModel.getParameter(newIndex)));
        table.setRowSelectionInterval(newIndex, newIndex);
        updateButtons();
    }
}//GEN-LAST:event_downButtonActionPerformed

private void removeButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_removeButtonActionPerformed
    int selectedRow = table.getSelectedRow();
    if (selectedRow > -1) {
        tableModel.removeParameter(selectedRow);
    }
    if (selectedRow == table.getRowCount()) {
        selectedRow--;
    }
    table.getSelectionModel().setSelectionInterval(selectedRow, selectedRow);
    updateButtons();
}//GEN-LAST:event_removeButtonActionPerformed

private void addButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_addButtonActionPerformed
    int index = tableModel.addParameter();
    table.getSelectionModel().setSelectionInterval(index, index);
    table.getColumnModel().getSelectionModel().setSelectionInterval(COL_NAME_INDEX, COL_NAME_INDEX);
    updateButtons();
}//GEN-LAST:event_addButtonActionPerformed
    
    
    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JButton addButton;
    private javax.swing.JButton downButton;
    private javax.swing.JScrollPane jScrollPane1;
    private javax.swing.JButton removeButton;
    private javax.swing.JTable table;
    private javax.swing.JButton upButton;
    // End of variables declaration//GEN-END:variables
    
    private void updateButtons() {
        int selIndex = table.getSelectedRow();
        boolean oneSelected = table.getSelectedRowCount() == 1;

        removeButton.setEnabled(oneSelected);
        upButton.setEnabled(oneSelected && (selIndex > 0));
        downButton.setEnabled(oneSelected && (selIndex < tableModel.getRowCount() - 1));
    }
    
    // accessible for test
    static class ParamsTableModel extends AbstractTableModel {
        
        private final List<MethodModel.Variable> parameters;
        
        public ParamsTableModel(List<MethodModel.Variable> parameters) {
            this.parameters = new ArrayList<MethodModel.Variable>(parameters);
        }
        
        public List<MethodModel.Variable> getParameters() {
            return parameters;
        }
        
        public int addParameter() {
            String name = generateUniqueName("parameter");  // NOI18N
            MethodModel.Variable parameter = MethodModel.Variable.create("java.lang.String", name, false);  // NOI18N
            int index = parameters.size();
            parameters.add(parameter);
            fireTableRowsInserted(index, index);
            return index;
        }
        
        public void removeParameter(int index) {
            parameters.remove(index);
            fireTableRowsDeleted(index, index);
        }
        
        public MethodModel.Variable getParameter(int index) {
            return parameters.get(index);
        }
        
        public MethodModel.Variable set(int index, MethodModel.Variable parameter) {
            return parameters.set(index, parameter);
        }
        
        public int getRowCount() {
            return parameters.size();
        }
        
        public int getColumnCount() {
            return 3;
        }
        
        public Object getValueAt(int row, int column) {
            Object result = null;
            if (row >= 0) {
                MethodModel.Variable parameter = parameters.get(row);
                if (parameter != null) {
                    switch (column) {
                        case COL_NAME_INDEX: result = parameter.getName(); break;
                        case COL_TYPE_INDEX: result = parameter.getType(); break;
                        case COL_FINAL_INDEX: result = parameter.getFinalModifier(); break;
                        default:
                    }
                }
            }
            return result;
        }
        
        @Override
        public String getColumnName(int column) {
            return columnNames[column];
        }
        
        @Override
        public boolean isCellEditable(int row, int column) {
            return true;
        }
        
        @Override
        public void setValueAt(Object aValue, int row, int column) {
            // check if inserted name is valid Java identifier
            // if not, fall back to old value
            if (column == COL_NAME_INDEX) {
                String insertedName = (String) aValue;
                if (!Utilities.isJavaIdentifier(insertedName.trim())) {
                    return;
                }
            }
            MethodModel.Variable parameter = parameters.get(row);
            MethodModel.Variable changedParameter = MethodModel.Variable.create(
                    column == COL_TYPE_INDEX ? chooseType(aValue, parameter.getType()) : parameter.getType(),
                    column == COL_NAME_INDEX ? chooseName(aValue, parameter.getName()) : parameter.getName(),
                    column == COL_FINAL_INDEX ? (Boolean) aValue : parameter.getFinalModifier()
                    );
            parameters.set(row, changedParameter);
            fireTableCellUpdated(row, column);
        }
        
        // JTable uses this method to determine the default renderer/editor for each cell.
        // If we didn't implement this method, then the last column would contain
        // text ("true"/"false"), rather than a check box.
        @Override
        public Class getColumnClass(int c) {
            return getValueAt(0, c).getClass();
        }
        
        private String generateUniqueName(String name) {
            List<Integer> numberSuffixes = new ArrayList<Integer>();
            for (MethodModel.Variable variable : parameters) {
                if (!name.equals(variable.getName()) && variable.getName().startsWith(name)) {
                    String suffix = variable.getName().substring(name.length());
                    if (isNumber(suffix)) {
                        numberSuffixes.add(Integer.parseInt(suffix));
                    }
                }
            }
            Collections.sort(numberSuffixes);
            String result = name;
            if (numberSuffixes.size() > 0) {
                int newSuffix = numberSuffixes.get(numberSuffixes.size() - 1) + 1;
                result = name + newSuffix;
            } else if (parameters.size() > 0) {
                result = name + 1;
            }
            return result;
        }
        
        private boolean isNumber(String value) {
            for (char character : value.toCharArray()) {
                if (!Character.isDigit(character)) {
                  return false;
                }
            }
          return true;//!value.trim().equals("");
        }
        
    }

    private static String chooseType(Object aValue, String typeName) {
        if (!(aValue instanceof String)) {
            return "*";  // NOI18N
        }
        String aValueString = ((String)aValue).trim();
        if (aValueString.equals("")) {  // NOI18N
            return typeName;
        }
        return aValueString;
    }

    private static String chooseName(Object aValue, String name) {
        String aValueString = ((String)aValue).trim();
        if (aValueString.equals("")) {  // NOI18N
            return name;
        }
        return aValueString;
    }
        
    private class ListSelectionListenerImpl implements ListSelectionListener {

        public void valueChanged(ListSelectionEvent e) {
            table.editCellAt(table.getSelectedRow(), table.getSelectedColumn());
            Component editor = table.getEditorComponent();
            if (editor instanceof JComboBox) {
                editor = ((JComboBox) editor).getEditor().getEditorComponent();
            }
            if (editor != null) {
                editor.requestFocus();
            }
            if (editor instanceof JTextComponent) {
                JTextComponent textComp = (JTextComponent) editor;
                textComp.selectAll();
            }
        }

    }

    private class TypeComboListener implements ActionListener {
        private JComboBox typeCombo;
        
        TypeComboListener(JComboBox combo) {
            this.typeCombo = combo;
        }
        
        public void actionPerformed(ActionEvent e) {
            int selectedRow = table.getSelectedRow();
            if (selectedRow >= 0) {
                String typeInModel = tableModel.getValueAt(selectedRow, COL_TYPE_INDEX).toString();
                if ("*".equals(typeInModel) && typeCombo.getSelectedItem() != null) {  // NOI18N
                    tableModel.setValueAt(typeCombo.getSelectedItem(), selectedRow, COL_TYPE_INDEX);
                }
            }
        }
    }
    
}
